﻿using System.Globalization;
using System.IO.Compression;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace Devonline.AspNetCore;

/// <summary>
/// 文件操作类服务
/// </summary>
/// <param name="logger">日志</param>
/// <param name="httpSetting">Http配置项</param>
public class FileService(ILogger<FileService> logger, HttpSetting httpSetting) : IFileService
{
    protected readonly ILogger<FileService> _logger = logger;
    protected readonly HttpSetting _httpSetting = httpSetting;
    protected readonly string _userAccess = $"In access index {KeyGenerator.GetStringKey()}";

    /// <summary>
    /// 从 fileName 获取文件扩展名为 extension 的同名文件
    /// </summary>
    /// <param name="fileName">原始文件名</param>
    /// <param name="extension">文件扩展名</param>
    /// <returns></returns>
    public virtual string GetSameFileName(string fileName, string? extension = default) => Path.Combine(Path.GetDirectoryName(fileName)!, Path.GetFileNameWithoutExtension(fileName))! + (extension ?? string.Empty);
    /// <summary>
    /// 获取附件绝对地址
    /// </summary>
    /// <param name="fileName">附件文件名</param>
    /// <returns></returns>
    public virtual string GetAttachmentPath(string fileName) => Path.Combine((_httpSetting.Attachment?.RootPath ?? DEFAULT_ATTACHMENT_PATH).GetAbsolutePath(), fileName);
    /// <summary>
    /// 获取随机的临时文件名
    /// </summary>
    /// <param name="extension">文件扩展名</param>
    /// <returns></returns>
    public virtual string GetTempFileName(string? extension = default)
    {
        var filePath = DateTime.UtcNow.ToString(DEFAULT_DATE_FORMAT, CultureInfo.InvariantCulture);
        var attachmentPath = GetAttachmentPath(filePath);
        if (!Directory.Exists(attachmentPath))
        {
            Directory.CreateDirectory(attachmentPath);
        }

        extension ??= DEFAULT_TEMPORARY_FILE_EXTENSION;
        return Path.Combine(filePath, KeyGenerator.GetKey() + extension);
    }
    /// <summary>
    /// 根据文件名获取 ContentType
    /// </summary>
    /// <param name="fileName"></param>
    /// <returns></returns>
    public virtual string GetContentType(string fileName)
    {
        var contentType = ContentType.Default;
        var extension = fileName[(fileName.LastIndexOf(CHAR_DOT) + 1)..];
        var fieldInfo = typeof(ContentType).GetField(extension, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.IgnoreCase);
        if (fieldInfo is not null)
        {
            var fieldValue = fieldInfo.GetValue(null)?.ToString();
            if (!string.IsNullOrWhiteSpace(fieldValue))
            {
                contentType = fieldValue;
            }
        }

        return contentType;
    }

    /// <summary>
    /// 从文件名获取文件绝对地址, 如果文件名是来自互联网的路径, 则下载并保存到本地在返回
    /// </summary>
    /// <param name="fileName">相对文件名或互联网路径</param>
    /// <returns></returns>
    public virtual async Task<string?> GetFileAsync(string fileName)
    {
        var filePath = GetAttachmentPath(fileName);
        if (File.Exists(filePath))
        {
            return filePath;
        }

        //如果是一个互联网地址, 则先下载到本地文件
        if (fileName.Contains(DEFAULT_PROTOCOL_SPLITER))
        {
            return await DownloadAsync(fileName);
        }

        return filePath;
    }
    /// <summary>
    /// 从互联网下载文件到保存到本地临时文件
    /// </summary>
    /// <param name="fileName">互联网完全文件名</param>
    /// <returns></returns>
    /// <exception cref="FileLoadException"></exception>
    /// <exception cref="BadHttpRequestException"></exception>
    public virtual async Task<string> DownloadAsync(string fileName)
    {
        var fileExtension = Path.GetExtension(fileName);
        var allowFileExtensions = (_httpSetting.Attachment.Download.Extensions ?? DEFAULT_ALLOW_FILE_EXTENSIONS).Split(CHAR_COMMA, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (!allowFileExtensions.Contains(fileExtension))
        {
            throw new FileLoadException($"不允许下载的文件扩展名: " + fileExtension, fileName);
        }

        using var httpClient = new HttpClient();
        using var stream = await httpClient.GetStreamAsync(fileName);
        if (stream.CanRead && stream.Length > 0)
        {
            if (stream.Length > _httpSetting.Attachment.Download.Length)
            {
                stream.Close();
                throw new BadHttpRequestException($"文件 {fileName} 大小超过允许下载的文件大小限制!");
            }

            fileName = GetTempFileName(fileExtension);
            fileName = GetAttachmentPath(fileName);
            using var fileStream = new FileStream(fileName, FileMode.OpenOrCreate);
            await stream.CopyToAsync(fileStream);
            await fileStream.FlushAsync();
            stream.Close();
            fileStream.Close();
        }

        return fileName;
    }

    /// <summary>
    /// 压缩文件/文件夹, 之后执行委托方法
    /// </summary>
    /// <param name="filePath">文件/文件夹路径</param>
    public virtual string Compress(string filePath, Action<string>? action = default)
    {
        if (string.IsNullOrWhiteSpace(filePath))
        {
            throw new ArgumentNullException($"{_userAccess}: the file or path: {filePath} can not be null!");
        }

        var path = filePath;
        var isFile = false;
        var datetime = DateTime.Now.ToString(DEFAULT_FILE_DATETIME_FORMAT);
        if (File.Exists(filePath))
        {
            //如果是个文件, 在当前文件所在目录创建临时目录, 目录名字使用文件名+当前时间
            isFile = true;
            path = GetSameFileName(filePath) + CHAR_UNDERLINE + datetime;
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }

            _logger.LogDebug($"{_userAccess}: the file path: {filePath} is a file, will copy this file into same name directory then compress the directory!");

            //将原文件复制到临时文件夹中
            File.Copy(filePath, $"{path}/{Path.GetFileName(filePath)}", true);
        }
        else if (Directory.Exists(filePath))
        {
            //如果是个文件夹
            path += CHAR_UNDERLINE + datetime;
            _logger.LogDebug($"{_userAccess}: the file path: {filePath} is a directory, will compress all files of directory!");
        }
        else
        {
            throw new ArgumentNullException($"{_userAccess}: the file path: {filePath} is not a file or path name!");
        }

        //创建压缩文件
        var zipFile = path + DEFAULT_ZIP_FILE_EXTENSION;
        if (File.Exists(zipFile))
        {
            File.Delete(zipFile);
        }

        _logger.LogDebug($"{_userAccess}: the directory: {path} will be compress into zip file: {zipFile}!");
        ZipFile.CreateFromDirectory(path, zipFile);
        _logger.LogInformation($"{_userAccess}: the directory: {path} has been compressed into zip file: {zipFile}!");

        if (File.Exists(zipFile) && action is not null)
        {
            _logger.LogInformation($"{_userAccess}: the action has value, execue the action, and then delete the zip file: {zipFile}!");
            action(zipFile);
            Delete(zipFile);
        }

        if (isFile)
        {
            _logger.LogDebug($"{_userAccess}: finally, delete the source directory: {path}!");
            Directory.Delete(path, true);
        }

        return zipFile;
    }

    /// <summary>
    /// 删除附件物理文件, 请在附件相关操作的事物提交之后执行此方法
    /// </summary>
    /// <param name="fileName">待删除的文件</param>
    public virtual void Delete(string fileName)
    {
        try
        {
            _logger.LogInformation($"{_userAccess}: the file {fileName} will be deleted!");
            if (!File.Exists(fileName))
            {
                fileName = GetAttachmentPath(fileName);
            }

            if (File.Exists(fileName))
            {
                _logger.LogDebug($"{_userAccess}: the file {fileName} exist, then will delete");
                File.Delete(fileName);
            }

            var filePath = Path.GetDirectoryName(fileName);
            if (Directory.Exists(filePath))
            {
                //如果存在文件缩略图则删除
                var relatedFileName = Path.Combine(filePath, _httpSetting.Attachment?.Thumbnail.Prefix ?? DEFAULT_IMAGE_THUMBNAIL_PREFIX + Path.GetFileName(fileName));
                if (File.Exists(relatedFileName))
                {
                    _logger.LogDebug($"{_userAccess}: the image file {fileName} thumbnail file exist, then will delete thumbnail file");
                    File.Delete(relatedFileName);
                }

                //如果存在文件裁剪图则删除
                relatedFileName = Path.Combine(filePath, _httpSetting.Attachment?.Crop.Prefix ?? DEFAULT_IMAGE_CROP_PREFIX + Path.GetFileName(fileName));
                if (File.Exists(relatedFileName))
                {
                    _logger.LogDebug($"{_userAccess}: the image file {fileName} crop file exist, then will delete file crop file");
                    File.Delete(relatedFileName);
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"{_userAccess}: the file {fileName} delete throw exception");
        }
    }
    /// <summary>
    /// 删除附件物理文件, 请在附件相关操作的事物提交之后执行此方法
    /// </summary>
    /// <param name="files">待删除的文件</param>
    public virtual void Delete(params string[] files)
    {
        if (files.Length == 0)
        {
            return;
        }

        _logger.LogInformation($"{_userAccess}: user will delete the files <{files.ToString<string>()}> from file system");
        var result = Parallel.ForEach(files, Delete);
        if (result.IsCompleted)
        {
            _logger.LogInformation($"{_userAccess}: user delete the files <{files.ToString<string>()}> from file system success");
        }
    }

    /// <summary>
    /// 销毁文件, 如果销毁, 此接口将性能不好且文件则无法找回
    /// 销毁附件物理文件, 请在附件相关操作的事物提交之后执行此方法
    /// </summary>
    /// <param name="fileName">待销毁的文件</param>
    public virtual async Task DestoryAsync(string fileName)
    {
        try
        {
            _logger.LogInformation($"{_userAccess}: the file {fileName} will be destoried");
            if (!File.Exists(fileName))
            {
                fileName = GetAttachmentPath(fileName);
            }

            if (File.Exists(fileName))
            {
                _logger.LogDebug($"{_userAccess}: the file {fileName} exist, then will be destoried");
                await DestoryAsync(fileName);
            }

            //如果存在文件缩略图则删除
            var filePath = Path.GetDirectoryName(fileName);
            if (Directory.Exists(filePath))
            {
                //如果存在文件缩略图则删除
                var relatedFileName = Path.Combine(filePath, _httpSetting.Attachment?.Thumbnail.Prefix ?? DEFAULT_IMAGE_THUMBNAIL_PREFIX + Path.GetFileName(fileName));
                if (File.Exists(relatedFileName))
                {
                    _logger.LogDebug($"{_userAccess}: the image file {fileName} thumbnail file exist, then will destory thumbnail file");
                    await DestoryAsync(relatedFileName);
                }

                //如果存在文件裁剪图则删除
                relatedFileName = Path.Combine(filePath, _httpSetting.Attachment?.Crop.Prefix ?? DEFAULT_IMAGE_CROP_PREFIX + Path.GetFileName(fileName));
                if (File.Exists(relatedFileName))
                {
                    _logger.LogDebug($"{_userAccess}: the image file {fileName} crop file exist, then will destory file crop file");
                    await DestoryAsync(relatedFileName);
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"{_userAccess}: the file {fileName} destory throw exception");
        }
    }
    /// <summary>
    /// 销毁文件, 如果销毁, 此接口将性能不好且文件则无法找回
    /// 销毁附件物理文件, 请在附件相关操作的事物提交之后执行此方法
    /// </summary>
    /// <param name="files">待销毁的文件</param>
    public virtual async Task DestoryAsync(params string[] files)
    {
        if (files.Length == 0)
        {
            return;
        }

        _logger.LogInformation($"{_userAccess}: user will destory the files <{files.ToString<string>()}> from file system");
        Parallel.ForEach(files, async file => await DestoryAsync(file));
        await Task.CompletedTask;
    }
    /// <summary>
    /// 销毁文件, 销毁后的文件不可还原
    /// </summary>
    /// <param name="fileName">要销毁的文件名</param>
    /// <param name="filler">默认填充符, 默认: 0x00</param>
    /// <param name="delete">是否删除, 默认: 是</param>
    /// <returns></returns>
    public virtual async Task DestoryAsync(string fileName, byte filler = 0x00, bool delete = true)
    {
        if (!File.Exists(fileName))
        {
            throw new FileNotFoundException("文件不存在!", fileName);
        }

        var fileInfo = new FileInfo(fileName);
        var bufferLength = (int)((fileInfo.Length >= AppSettings.UNIT_KILO) ? AppSettings.UNIT_KILO : fileInfo.Length);
        var last = fileInfo.Length;
        var buffer = new byte[bufferLength];
        for (int index = AppSettings.UNIT_ZERO; index < bufferLength; index++)
        {
            buffer[index] = filler;
        }

        using var fs = fileInfo.Open(FileMode.Open, FileAccess.Write);
        fs.Position = AppSettings.UNIT_ZERO;
        while (last >= bufferLength)
        {
            await fs.WriteAsync(buffer.AsMemory(AppSettings.UNIT_ZERO, bufferLength)).ConfigureAwait(false);
            last -= bufferLength;
        }

        if (last > AppSettings.UNIT_ZERO)
        {
            await fs.WriteAsync(buffer.AsMemory(AppSettings.UNIT_ZERO, (int)last)).ConfigureAwait(false);
        }

        fs.Close();
        await fs.DisposeAsync().ConfigureAwait(true);

        if (delete)
        {
            fileInfo.Delete();
        }
    }
}